/*
  author Sylvain Bertrand <digital.ragnarok@gmail.com>
  Protected by GNU Affero GPL v3 with some exceptions.
  See README at root of alga tree.
*/
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/cdev.h>

#include "types.h"
#include "ba.h"
#include "ucode.h"
#include "cp.h"
#include "gpu.h"
#include "irq.h"
#include "drv.h"
#include "mc.h"
#include "regs.h"

#include "default_state.h"

#define	PACKET_TYPE2	2
#define	PACKET_TYPE3	3
#define PACKET2		(PACKET_TYPE2 << 30)
#define PACKET3(op,n)	((PACKET_TYPE3 << 30) \
			| (((op) & 0xff) << 8) \
			| ((n) & 0x3fff) << 16)

/* packet 3 operations */
#define	PACKET3_CLEAR_STATE			0x12
#define	PACKET3_ME_INITIALIZE			0x44
#define		PACKET3_ME_INIT_DEV_ID(x)		((x) << 16)
#define	PACKET3_PREAMBLE_CTL			0x4a
#define		PACKET3_PREAMBLE_BEGIN_CLEAR_STATE	(2 << 28)
#define		PACKET3_PREAMBLE_END_CLEAR_STATE	(3 << 28)
#define	PACKET3_SET_CTX_REG			0x69
#define		PACKET3_SET_CTX_REG_START		0x00028000
#define		PACKET3_SET_CTX_REG_END			0x00029000
#define	PACKET3_SET_CTL_CONST			0x6f
#define		PACKET3_SET_CTL_CONST_START		0x0003cff0
#define		PACKET3_SET_CTL_CONST_END		0x0003ff0c

void cp_stop(struct pci_dev *dev)
{
	wr32(dev, CP_ME_HALT | CP_PFP_HALT, CP_ME_CTL);
	wr32(dev, 0, SCRATCH_UMSK);
}

void cp_start(struct pci_dev *dev)
{
	struct dev_drv_data *drv_data;
	unsigned wptr;
	unsigned i;
	unsigned padding_dws;
	u32 *ring;

	drv_data = pci_get_drvdata(dev);
	ring = drv_data->ba.cp_ring.cpu_addr;

	ring[0] = PACKET3(PACKET3_ME_INITIALIZE, 5);
	ring[1] = 0x1;
	ring[2] = 0x0;
	ring[3] = drv_data->gpu.cfg.max_hw_ctxs - 1;
	ring[4] = PACKET3_ME_INIT_DEV_ID(1);
	ring[5] = 0;
	ring[6] = 0;

	/* padding on fetch size, namely 16 dws */
	ring[7] = PACKET2;
	ring[8] = PACKET2;
	ring[9] = PACKET2;
	ring[10] = PACKET2;
	ring[11] = PACKET2;
	ring[12] = PACKET2;
	ring[13] = PACKET2;
	ring[14] = PACKET2;
	ring[15] = PACKET2;

	wmb();

	drv_data->cp0.wptr = 16;
	wr32(dev, drv_data->cp0.wptr, CP_RB_WPTR);
	rr32(dev, CP_RB_WPTR);

	wr32(dev, 0xff, CP_ME_CTL);

	/* setup clear context state */
	wptr = drv_data->cp0.wptr;
	ring[wptr++] = PACKET3(PACKET3_PREAMBLE_CTL, 0);
	ring[wptr++] = PACKET3_PREAMBLE_BEGIN_CLEAR_STATE;

	for(i = 0; i < (sizeof(default_state) / 4); ++i)
		ring[wptr++] = default_state[i];

	ring[wptr++] = PACKET3_PREAMBLE_END_CLEAR_STATE;

	/* set clear context state */
	ring[wptr++] = PACKET3(PACKET3_CLEAR_STATE, 0);
	ring[wptr++] = 0;

	/* SQ_VTX_BASE_VTX_LOC */
	ring[wptr++] = PACKET3(PACKET3_SET_CTL_CONST, 2);
	ring[wptr++] = 0;
	ring[wptr++] = 0;
	ring[wptr++] = 0;

	/* clear consts */
	ring[wptr++] = PACKET3(PACKET3_SET_CTL_CONST, 3);
	ring[wptr++] = 0x00000bc4;
	ring[wptr++] = 0xffffffff;
	ring[wptr++] = 0xffffffff;
	ring[wptr++] = 0xffffffff;

	ring[wptr++] = PACKET3(PACKET3_SET_CTX_REG, 2);
	ring[wptr++] = 0x00000316;
	ring[wptr++] = 0x0000000e;
	/* VGT_VERTEX_REUSE_BLK_CTL */
	ring[wptr++] = 0x00000010;

	if (wptr & (16 - 1))
		padding_dws = 16 - (wptr & (16 - 1)); /* no ring loop */
	else
		padding_dws = 0;
	for(i = 0; i < padding_dws; ++i)
		ring[wptr++] = PACKET2;

	wmb();

	drv_data->cp0.wptr = wptr;
	wr32(dev, drv_data->cp0.wptr, CP_RB_WPTR);
	rr32(dev, CP_RB_WPTR);
}

/*
 * o ring size is 2^CP_RING_LOG2_QWS(17) quadwords (256 * 4096 bytes)
 * o block size is gpu page size, namely 2^GPU_PAGE_LOG2_QWS(9) quadwords
 *   (4096 bytes)
 * o ring is put right after ih ring (2^IH_RING_LOG2_DWS(14) dwords)
 * o writeback was put right before the ih ring
 */
#define WB_SCRATCH_OFFSET 0
#define WB_CP_RPTR_OFFSET 1024
void cp_init(struct pci_dev *dev)
{
	u32 cp_rb_ctl;
	gpu_addr_t cp_rb_rptr_addr;
	gpu_addr_t wb_page_addr;
	gpu_addr_t cp_addr;
	gpu_addr_t wb_scratch_addr;
	struct dev_drv_data *drv_data;
	

	wb_page_addr = rr32(dev, CFG_MEM_SZ) * 1024 * 1024;
	cp_addr = wb_page_addr + GPU_PAGE_SZ + (1 << IH_RING_LOG2_DWS) * 4;

	/* reset cp; if cp is reset, then pa, sh, vgt also need to be reset */
	wr32(dev, SOFT_RESET_CP | SOFT_RESET_PA | SOFT_RESET_SH
			| SOFT_RESET_VGT | SOFT_RESET_SX, GRBM_SOFT_RESET);
	rr32(dev, GRBM_SOFT_RESET);
	mdelay(15);
	wr32(dev, 0, GRBM_SOFT_RESET);
	rr32(dev, GRBM_SOFT_RESET);

	/* set ring buffer size */
	cp_rb_ctl = RB_BLK_LOG2_QWS(GPU_PAGE_LOG2_QWS)
					| RB_BUF_LOG2_QWS(CP_RING_LOG2_QWS);
#ifdef __BIG_ENDIAN
	cp_rb_ctl |= BUF_SWAP_32BIT;
#endif
	wr32(dev, cp_rb_ctl, CP_RB_CTL);
	wr32(dev, 0x4, CP_SEM_WAIT_TIMER);

	wr32(dev, 0, CP_RB_WPTR_DELAY);

	wr32(dev, cp_rb_ctl | RB_RPTR_WR_ENA, CP_RB_CTL);
	wr32(dev, 0, CP_RB_RPTR_WR);
	wr32(dev, 0, CP_RB_WPTR);

	/* set the wb address, enabled or not, 2 lower bits are for endian */
	cp_rb_rptr_addr = wb_page_addr + WB_CP_RPTR_OFFSET;
	
	wr32(dev, cp_rb_rptr_addr & 0xfffffffc, CP_RB_RPTR_ADDR);
	wr32(dev, upper_32_bits(cp_rb_rptr_addr) & 0xff, CP_RB_RPTR_ADDR_HI);

	wb_scratch_addr = wb_page_addr + WB_SCRATCH_OFFSET;
	/* 256 bytes block index, ok because address is GPU_PAGE_SZ aligned */
	wr32(dev, (wb_scratch_addr >> 8) & 0xffffffff, SCRATCH_ADDR);

	wr32(dev, 0xff, SCRATCH_UMSK);

	mdelay(1);
	wr32(dev, cp_rb_ctl, CP_RB_CTL);
	
	/* 256 bytes aligned ok because it is GPU_PAGE_SZ aligned */
	wr32(dev, cp_addr >> 8, CP_RB_BASE); /* 256 bytes block index */
	wr32(dev, (1 << 27) | (1 << 28), CP_DEBUG); /* XXX: ??? */

	/*
	 * drv_data->cp0.rptr = rr32(dev, CP_RB_RPTR);
	 * drv_data->cp0.wptr = rr32(dev, CP_RB_WPTR);
	 * should be 0
	 */
	drv_data = pci_get_drvdata(dev);

	drv_data->cp0.rptr = 0;
	drv_data->cp0.wptr = 0;
}
